////////////////////////////////////////////////////////////////////////////////
//
//  Copyright (C) 2003-2006 Adobe Macromedia Software LLC and its licensors.
//  All Rights Reserved. The following is Source Code and is subject to all
//  restrictions on such code as contained in the End User License Agreement
//  accompanying this product.
//
////////////////////////////////////////////////////////////////////////////////

package mx.charts.chartClasses
{

import flash.display.DisplayObject;
import mx.charts.HitData;
import mx.collections.ArrayCollection;
import mx.collections.CursorBookmark;
import mx.collections.ICollectionView;
import mx.collections.IViewCursor;
import mx.core.UIComponent;
import mx.core.mx_internal;
import mx.events.CollectionEvent;
import mx.managers.ISystemManager;
import mx.managers.SystemManager;
import mx.resources.ResourceBundle;
import flash.utils.Dictionary;

use namespace mx_internal;

/**
 *  Stacked Series serves as the common base class
 *  for all of the stacking set series (BarSet, ColumnSet, and AreaSet).
 *  A StackedSeries accepts an Array of sub-series elements and does
 *  the appropriate calculations to stack them vertically so that each series
 *  renders the sum of the previous series's data plus its own value.
 *  This class is not intended for direct use.  
 */
public class StackedSeries extends Series
{
    include "../../core/Version.as";

	//--------------------------------------------------------------------------
	//
	//  Class initialization
	//
	//--------------------------------------------------------------------------	

	loadResources();
	
	//--------------------------------------------------------------------------
	//
	//  Class resources	
	//
	//--------------------------------------------------------------------------
    
	[ResourceBundle("charts")]
	
	/**
	 *  @private
     */	
	private static var packageResources:ResourceBundle;
	
	/**
	 *  @private
     */	
	private static var resourceIncorrectStacking:String;

	//--------------------------------------------------------------------------
	//
	//  Class methods
	//
	//--------------------------------------------------------------------------
 
    /**
	 *  @private    
     *  Loads resources for this class.
     */
	private static function loadResources():void
	{		
		resourceIncorrectStacking =
			packageResources.getString("incorrectStacking"); 
	}
			
	//--------------------------------------------------------------------------
	//
	//  Constructor
	//
	//--------------------------------------------------------------------------

	/**
	 *  Constructor.
	 */
	public function StackedSeries()
	{
		super();

		invalidateProperties();
	}
	
	//--------------------------------------------------------------------------
	//
	//  Variables
	//
	//--------------------------------------------------------------------------

	/**
	 *  @private
	 */
	private var _seriesDirty:Boolean = true;

	/**
	 *  The summed totals of the stacked values.
	 *  This property contains a Dictionary whose keys are the values
	 *  represented by the child series along the primary axis
	 *  (for example, x axis values for a ColumnSeries, y axis values for a BarSeries),
	 *  and whose values are the summed total of all the child series values
	 *  at that key.
	 */
	protected var totalsByPrimaryAxis:Dictionary;

	/**
	 *  The maximum sum represented by this stacked series.
	 */
	protected var stackedMaximum:Number;
	
	/**
	 *  @private
	 */
	private var stackingDirty:Boolean = true;

	//--------------------------------------------------------------------------
	//
	//  Overridden properties: ChartElement
	//
	//--------------------------------------------------------------------------

	//----------------------------------
	//  workingDataProvider
	//----------------------------------

	/**
	 *  @private
	 */
	override protected function processNewDataProvider(value:Object):void
	{
		super.processNewDataProvider(value);

		var n:int = _series.length;
		for (var i:int = 0; i < n; i++)
		{
			IChartElement(_series[i]).chartDataProvider = dataProvider;			
		}
	}

	//--------------------------------------------------------------------------
	//
	//  Properties
	//
	//--------------------------------------------------------------------------

	//----------------------------------
	//  series
	//----------------------------------

	/**
	 *  @private
	 *  Storage for the series property.
	 */
	private var _series:Array = [];
	
	[Inspectable]

	/**
	 *  An array of sub-series managed by this stacking set.
	 *  These series are rendered according to the stacking behavior
	 *  of this stacking set as defined by the value
	 *  of the <code>type</code> property.
	 */
	public function get series():Array
	{
		return _series;
	}

	/**
	 *  @private
	 */
	public function set series(value:Array):void
	{
		_series = value == null ? [] : value;

		invalidateSeries();

		var s:ChartBase = chart;
		if (s)
			s.invalidateSeriesStyles();
	}

	//----------------------------------
	//  type
	//----------------------------------

	/**
	 *  @private
	 *  Storage for the type property.
	 */
	private var _type:String;	
	
	[Inspectable]

	/**
	 *  The grouping behavior of this series.
	 *  All stacking series support <code>"overlaid"</code>,
	 *  <code>"stacked"</code>, and <code>"100%"</code>.
	 *  When the <code>type</code> property is <code>"overlaid"</code>,
	 *  all sub-series are rendererd normally, with no special behavior applied.
	 *  When the <code>type</code> property is <code>"stacked"</code>,
	 *  each sub-series is rendered as the sum of its data
	 *  plus the values of all previous series.
	 *  When the <code>type</code> property is <code>"100%"</code>,
	 *  each sub-series is rendered as its portion of the total sum
	 *  of all series.
	 */
	public function get type():String
	{
		return _type;
	}

	/**
	 *  @private
	 */
	public function set type(value:String):void
	{
		invalidateStacking();
		invalidateSeries();
		
		_type = value;
	}

	//--------------------------------------------------------------------------
	//
	//  Overridden methods: UIComponent
	//
	//--------------------------------------------------------------------------
	
	/**
	 *  @private
	 */
	override protected function commitProperties():void
	{
		super.commitProperties();

		updateStacking();

		if (_seriesDirty)
		{
			_seriesDirty = false;
			buildSubSeries();
		}
	}	

	/**
	 *  @private
	 */
	override protected function updateDisplayList(unscaledWidth:Number,
												  unscaledHeight:Number):void
	{
		super.updateDisplayList(unscaledWidth, unscaledHeight);

		var n:int = numChildren;
		for (var i:int = 0; i < n; i++)
		{
			UIComponent(getChildAt(i)).setActualSize(unscaledWidth,
													 unscaledHeight);
		}
	}
	
	//--------------------------------------------------------------------------
	//
	//  Overridden methods: ChartElement
	//
	//--------------------------------------------------------------------------

	/**
	 *  @private
	 */
	override public function describeData(dimension:String,
										  requiredFields:uint):Array
	{
		updateStacking();
		validateData();
		
		var desc:DataDescription;
		var result:Array = [];
		var n:int;
		var i:int;
		
		if (type == "100%")
		{
			if (dimension == CartesianTransform.VERTICAL_AXIS)
			{
				desc = new DataDescription();
				desc.min = 0;
				desc.max = 100;
				result = [ desc ];
			}
			else
			{
				n = series.length;
				for (i = 0; i < n; i++)
				{
					result = result.concat(
						series[i].describeData(dimension, requiredFields));
				}
			}
		}

		else if (type == "stacked")
		{
			if (dimension == CartesianTransform.VERTICAL_AXIS)
			{
				var vCache:Array = [ { value: 0}, { value: stackedMaximum } ];
				dataTransform.getAxis(CartesianTransform.VERTICAL_AXIS).
					mapCache(vCache, "value", "number");
				desc = new DataDescription();
				desc.min = vCache[0].number;
				desc.max = vCache[1].number;			
				result = [ desc ];
			}
			else
			{
				n = series.length;
				for (i = 0; i < n; i++)
				{
					result = result.concat(
						series[i].describeData(dimension, requiredFields));
				}
			}
		}

		else
		{
			n = series.length;
			for (i = 0; i < n; i++)
			{
				result = result.concat(
					series[i].describeData(dimension, requiredFields));
			}
		}

		return result;		
	}

	/**
	 *  @private
	 */
	override public function findDataPoints(x:Number, y:Number,
											sensitivity:Number):Array
	{
		var hds:Array = super.findDataPoints(x, y, sensitivity);
		
		var n:int = hds.length;
		if (n > 0 && (_type == "stacked" || _type == "100%"))
		{
			for (var i:int = 0; i < n; i++)
			{
				hds[i].dataTipFunction = formatDataTip;	
			}
		}

		return hds;
	}

	/**
	 *  @private
	 */
	override public function chartStateChanged(oldState:uint, v:uint):void
	{
		updateData();

		super.chartStateChanged(oldState,v);
	}
	
	/**
	 *  @private
	 */
	override public function mappingChanged():void
	{
		var n:int = numChildren;
		for (var i:int = 0; i < n; i++)
		{
			var c:IChartElement = getChildAt(i) as IChartElement;
			if (c)
				c.mappingChanged();
		}

		super.mappingChanged();
	}

	/**
	 *  @private
	 */
	override public function claimStyles(styles:Array,
										 firstAvailable:uint):uint
	{
		var n:int = _series.length;
		for (var i:int = 0; i < n; i++)
		{
			var c:IChartElement = _series[i];
			firstAvailable = c.claimStyles(styles, firstAvailable);
		}

		return firstAvailable;
	}

	//--------------------------------------------------------------------------
	//
	//  Overridden methods: Series
	//
	//--------------------------------------------------------------------------

	/**
	 *  @private
	 */
	override public function get legendData():Array
	{
		var keyItems:Array = [];
		
		var n:int = _series.length;
		for (var i:int = 0; i < n; i++)
		{
			var ld:Object = _series[i].legendData;
			if (ld)
				keyItems = keyItems.concat(ld);
		}
		
		return keyItems;
	}

	//--------------------------------------------------------------------------
	//
	//  Methods
	//
	//--------------------------------------------------------------------------

	/**
	 *  Call this method to trigger a call to the <code>buildSubSeries()</code>
	 *  method on the next call to the <code>commitProperties()</code> method.  
	 */
	protected function invalidateSeries():void
	{
		if (_seriesDirty == false)
		{
			_seriesDirty = true;

			invalidateProperties();
		}
	}
	
	/**
	 *  Call this method to trigger a regeneration of the stacked values
	 *  on the next call to the <code>commitProperties()</code> method.  
	 */
	public function invalidateStacking():void
	{
		if (stackingDirty == false)
		{
			stackingDirty = true;

			invalidateProperties();					
		}
	}

	/** 
	 *  Applies any customization to a sub-series
	 *  when building the stacking behavior.
	 *  By default, this method assigns the inherited data providers
	 *  to the sub-series.
	 *  Derived classes can override this method
	 *  to apply further customization.
	 */
	protected function customizeSeries(g:IChartElement, i:uint):void
	{
		g.chartDataProvider = dataProvider;			
	}

	/**
	 *  Processes the Array of sub-series for display, when necessary.
	 *  This method ensures that all sub-series are added as children of this
	 *  stacking set, and applies any per-series customization that is necessary
	 *  (for example, assigning inherited data providers or clustering properties).
	 *  <p>This method is also responsible for informing the chart that series
	 *  have changed and, as a result, implicit series styles must be reassigned.
	 *  This method is called automatically by the stacking set, when necessary.
	 *  Instead of calling this method directly,
	 *  you should consider calling the <code>invalidateSeries()</code> method.</p>
	 */		
	protected function buildSubSeries():void
	{
		while (numChildren > 0)
		{
			removeChildAt(0);
		}

		for (var i:int = _series.length - 1; i >= 0; i--)
		{
			var g:IChartElement = IChartElement(_series[i]);
			customizeSeries(g, i);
			addChild(g as DisplayObject);
		}

		var s:ChartBase = chart;
		if (s)	
			s.invalidateSeriesStyles();
	}

	/**
	 *  Iterates over the individual sub-series to build the stacked values.
	 */
	protected function updateStacking():void
	{
		if (stackingDirty == false)
			return;

		stackingDirty = false;
		
		var stacker:StackedSeries = null;
		
		if (_series == null)
			return;

		if (_type == "stacked" || _type == "100%")
			stacker = this;

		var n:int = _series.length;
		for (var i:int = 0; i < n; i++) 
		{
			if (_series[i] is IStackable)
				_series[i].stacker = stacker;	
		}

		if (_type == "stacked" || _type == "100%")
			stack();
	}

	/**
	 *  Updates the series data, and uses the values of the series data
	 *  it is stacking on top of so it can stack correctly.
	 */
	public function stack():void
	{
		var n:int = _series.length;
		var i:int;
		var series:IStackable;

		totalsByPrimaryAxis = new Dictionary(false);
		
		stackedMaximum = 0;
		
		var previousSeries:IStackable = null;
		for (i = 0; i < n; i++)
		{
			series = _series[i];
			stackedMaximum = Math.max(stackedMaximum,
				series.stack(totalsByPrimaryAxis, previousSeries));
			previousSeries = series;
		}

		var totals:Dictionary = _type == "100%" ? totalsByPrimaryAxis : null;
		
		for (i = 0; i < n; i++)
		{
			series = _series[i];
			series.stackTotals = totals;
		}
	}

	/**
	 *  Provides custom text for DataTips.
	 *  Stacking sets override the DataTip text of their contained sub-series
	 *  to display additional information related to the stacking behavior.
	 *  Derived classes must override this method to define custom DataTip text.
	 */
	protected function formatDataTip(hitData:HitData):String
	{
		return "";
	}
}

}
